package org.jprobe.utils;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.util.jar.JarFile;
/**
 * Input/Output File operations factory
 *
 * @author Hussain Al-Mutawa
 * @version 1.0
 * @since 1.0
 */
public final class IOUtils {


	/** this class should not be initialized **/
	private IOUtils(){super();}

	/**
	 * Writes the contents to the given file.
	 * if the directory is not available, it will be made
	 *
	 * @param fileName the name including the absolute path on the disk
	 * @param contents the text contents to be written to the file
	 * @throws IOException if the path to the file does not exits, or writing to the disk is not possible
	 */
	public static void writeToFile(final String fileName, final String contents) throws IOException{
		writeToFile(new File(fileName),contents);
	}
	/**
	 * Writes the contents to the given file.
	 * if the directory is not available, it will be made
	 *
	 * @param file the file to write contents to it
	 * @param contents the text contents to be written to the file
	 * @throws IOException if the path to the file does not exits, or writing to the disk is not possible
	 */
	public static void writeToFile(final File file, final String contents) throws IOException{
		final File dir = file.getParentFile();

		if(dir==null || !(dir.exists() || dir.mkdirs())){
			throw new IOException("failed to write to file system / malformed file path");
		}

		final FileOutputStream fos = new FileOutputStream(file);
		final OutputStreamWriter outw = new OutputStreamWriter(fos);
		outw.write(contents);
		outw.close();
		fos.close();
	}

	/**
	 * Read a given file and returns the whole contents.
	 * <p>
	 * the returned text contains extra line break at the end,
	 * the consumer method should strip this line break if required
	 * </p>
	 * @param fileName the file to be read
	 * @return the text contents of the file
	 * @throws IOException if the file is not found or can not be read
	 */
	public static String readFromFileSys(final String fileName) throws IOException {
		return readFromFileSys(new File(fileName));
	}
	/**
	 * Read a given file and returns the whole contents.
	 * <p>
	 * the returned text contains extra line break at the end,
	 * the consumer method should strip this line break if required
	 * </p>
	 * @param file the file to be read
	 * @return the text contents of the file
	 * @throws IOException if the file is not found or can not be read
	 */
	public static String readFromFileSys(final File file) throws IOException {

		final FileInputStream fstream = new FileInputStream(file);
		final DataInputStream inStream = new DataInputStream(fstream);
		final BufferedReader buffer = new BufferedReader(new InputStreamReader(inStream));
		final StringBuilder text = new StringBuilder();
		for(String strLine = buffer.readLine(); strLine!=null; strLine = buffer.readLine()){text.append(strLine).append('\n');}

		inStream.close();
		fstream.close();

		return text.toString();
	}

	/**
	 * Deletes all files and subdirectories under dir recursively.
	 * Returns true if all deletions were successful.
	 * If a deletion fails, the method stops attempting to delete and returns false.
	 */
	public static boolean deleteDirectory(final String dirPath) {
		final File dir = new File(dirPath);
		if (dir.isDirectory()) {
			final String[] children = dir.list();
			for (int index=0; index<children.length; index++) {
				deleteDirectory(dirPath +'/'+ children[index]);
			}
		}
		// The directory is now empty so delete it
		return dir.delete();
	}

//	/**
//	 * gets the files in a directory filtered by an extension
//	 * @param dir the directory in file system
//	 * @param extension the last part of the file name
//	 * @return list of file names
//	 */
//	public static String[] getFiles(final File dir,final String extension){
//		return dir.list(getFilenameFilter(extension));
//	}



	/**
	 * Read a text file from a jar
	 * @param resourcePath the resource path
	 * @return the contents of the file
	 * @throws IOException if the resource does not exist or is corrupted
	 */
	public static String readFromJar(final String jarPath,final String resourcePath) throws IOException{
		final JarFile jar = new JarFile(jarPath);
		final StringBuffer sbuffer = new StringBuffer();
		final InputStream inStream =  jar.getInputStream(jar.getJarEntry(resourcePath)); //JavaTools.class.getResourceAsStream(resourcePath);
		final BufferedReader buffer = new BufferedReader(new InputStreamReader(inStream));
		for(String line = buffer.readLine();line!=null;line = buffer.readLine()){sbuffer.append(line).append('\n');}
		buffer.close();
		inStream.close();
		return sbuffer.toString();
	}

	/**
	 * Copies files, java method to copy files
	 * @param source the file name of the source item to copy
	 * @param target the file name of the destination item to be copied
	 * @throws IOException if any of the source or destination paths
	 * 		   is incorrect or the source file is corrupted
	 */
	public static void copy(final String source,final String target) throws IOException{

		// Getting file channels
		final FileChannel inChannel  = new FileInputStream(source).getChannel();
		final FileChannel outChannel = new FileOutputStream(target).getChannel();

		// JavaVM does its best to do this as native I/O operations.
		inChannel.transferTo (0, inChannel.size(), outChannel);

		// Closing file channels will close corresponding stream objects as well.
		outChannel.close();
		inChannel.close();

	}

	/**
	 * compares the contents of any two files using <code>BufferedInputStream</code>
	 * <p>
	 * A BufferedInputStream adds functionality to another input stream-namely,
	 * the ability to buffer the input and to support the mark and reset methods.
	 * When the BufferedInputStream is created, an internal buffer array is created.
	 * As bytes from the stream are read or skipped, the internal buffer is refilled
	 * as necessary from the contained input stream, many bytes at a time.
	 * The mark operation remembers a point in the input stream and the reset
	 * operation causes all the bytes read since the most recent mark operation
	 * to be reread before new bytes are taken from the contained input stream.
	 * </p>
	 * @param file1 the first file
	 * @param file2 the second file
	 * @return true if the two files have the same contents, false otherwise
	 * @throws IOException if the file path of any of the two files does not exist
	 * 					   or any of the two files is corrupted.
	 */
	public static boolean compareFilesWithBuffer(final File file1,final File file2) throws IOException{

		final boolean result;

        if(file1.length() == file2.length()){
        	result = compareFiles(
    				new BufferedInputStream(new FileInputStream(file1)),
    				new BufferedInputStream(new FileInputStream(file2))
    			);
        }else{
            result = false;
        }

        return result;
	}


	/**
	 * compares the contents of any two files using <code>FileInputStream</code>
	 *
	 * @param filePath1 the first file
	 * @param filePath2 the second file
	 * @return true if the two files have the same contents, false otherwise
	 * @throws IOException if the file path of any of the two files does not exist
	 * 					   or any of the two files is corrupted.
	 */
	public static boolean compareFiles(final File file1,final File file2) throws IOException{

		final boolean result;

        if(file1.length() == file2.length()){
        	result = compareFiles(
        				new FileInputStream(file1),
        				new FileInputStream(file2)
        			);
        }else{
            result = false;
        }

        return result;
	}

	public static boolean compareFiles(final InputStream is1,final InputStream is2) throws IOException{
		boolean result;

        int a=0,b=0;
        result = true;
        boolean loop = true;
        while(loop){
            a = is1.read();
            b = is2.read();
            if(a != b){
                result = false;
                break;
            }
            loop = !(a==-1 || b==-1);
        }

        return result;
	}

}
